package com.hcc.export.spring;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.hcc.export.annotation.CurPage;
import com.hcc.export.annotation.Export;
import com.hcc.export.annotation.PageSize;
import com.hcc.export.domain.IExportPageReq;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.NumberUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

/**
 * ExportMethodAspect
 *
 * @author hushengjun
 * @date 2025/4/19
 */
@Aspect
public class ExportMethodAop {

    @Pointcut("@annotation(com.hcc.export.annotation.Export)")
    public void exportMethodAspect() {}

    @Around("exportMethodAspect()")
    public Object prcAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Export exportAnnotation = AnnotationUtils.findAnnotation(methodSignature.getMethod(), Export.class);
        RequestMapping requestMappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(methodSignature.getMethod(), RequestMapping.class);

        Object[] args = joinPoint.getArgs();
        if (exportAnnotation == null || requestMappingAnnotation == null) {
            return joinPoint.proceed();
        }

        String[] paths = requestMappingAnnotation.path();
        String methodPath = paths[0];
        if (!methodPath.startsWith("/")) {
            methodPath = "/" + methodPath;
        }
        if (methodPath.endsWith("/")) {
            methodPath = methodPath.substring(0, methodPath.length() - 1);
        }

        String exportPath = methodPath + exportAnnotation.pathSuffix();

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

        String requestURI = request.getRequestURI();
        if (!requestURI.endsWith(exportPath)) {
            return joinPoint.proceed();
        }

        String filename = exportAnnotation.filename();
        if (filename.isEmpty()) {
            filename = "exportFile";
        }
        filename = exportAnnotation.filePath() + "/" + filename + "_" + System.currentTimeMillis() + ".xlsx";
        String encodeFilename = URLEncoder.encode(filename, "UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/octet-stream");
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", encodeFilename));

        // 开始分页
        int curPage = exportAnnotation.exportBeginPage();
        if (curPage < 0) {
            curPage = 1;
        }
        int pageSize = exportAnnotation.exportPageSize();
        if (pageSize < 0) {
            pageSize = 1000;
        }

        Annotation[][] methodParaAnnotations = methodSignature.getMethod().getParameterAnnotations();

        // 生成excel
        ExcelWriter excelWriter = EasyExcel.write(filename, exportAnnotation.entityClass()).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("Export").build();
        while (true) {
            Object[] newArgs = this.getNewArgs(args, methodParaAnnotations, curPage, pageSize);
            Object result = joinPoint.proceed(newArgs);
            if (result == null) {
                break;
            }

            JSONArray content = this.getContentForJsonArray(result, exportAnnotation);
            if (content == null) {
                break;
            }

            if (!CollectionUtils.isEmpty(content)) {
                // 处理分页数据，写入excel
                List<?> list = content.toJavaList(exportAnnotation.entityClass());
                excelWriter.write(list, writeSheet);
            }

            int size = content.size();
            if (CollectionUtils.isEmpty(content) || size < pageSize) {
                break;
            }

            curPage++;
        }

        excelWriter.finish();

        File file = new File(filename);
        if (!file.exists()) {
            throw new RuntimeException("导出文件不存在");
        }

        try (OutputStream outputStream = response.getOutputStream()) {
            byte[] bytes = Files.readAllBytes(Paths.get(filename));
            outputStream.write(bytes);
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            boolean isDeleted = file.delete();
        }

        return null;
    }

    /**
     * 获取新的参数
     *
     * @param args
     * @param methodParaAnnotations
     * @param curPage
     * @param pageSize
     * @return
     */
    private Object[] getNewArgs(Object[] args, Annotation[][] methodParaAnnotations, int curPage, int pageSize) {
        Object[] newArgs = Arrays.copyOf(args, args.length);
        for (int i = 0; i < args.length; i++) {
            Annotation[] argAnnotations = methodParaAnnotations[i];
            Object arg = args[i];

            boolean existCurPage = Arrays.stream(argAnnotations).anyMatch(a -> CurPage.class.equals(a.annotationType()));
            boolean existPageSize = Arrays.stream(argAnnotations).anyMatch(a -> PageSize.class.equals(a.annotationType()));
            if (existCurPage && existPageSize) {
                throw new RuntimeException("参数不能同时存在@CurPage和@PageSize");
            }
            if (existCurPage) {
                newArgs[i] = NumberUtils.parseNumber(String.valueOf(curPage), (Class<Number>) arg.getClass());
            }
            if (existPageSize) {
                newArgs[i] = NumberUtils.parseNumber(String.valueOf(pageSize), (Class<Number>) arg.getClass());
            }
            if (arg instanceof IExportPageReq) {
                IExportPageReq exportPageReq = (IExportPageReq) arg;
                exportPageReq.setCurPage(curPage);
                exportPageReq.setPageSize(pageSize);

                newArgs[i] = exportPageReq;
            }
        }

        return newArgs;
    }

    /**
     * 获取分页内容
     *
     * @param result
     * @param exportAnnotation
     * @return
     */
    private JSONArray getContentForJsonArray(Object result, Export exportAnnotation) {
        JSONArray content = null;
        Object jsonObj = JSON.parse(JSON.toJSONString(result));
        if (jsonObj instanceof JSONArray) {
            content = (JSONArray) jsonObj;
        } else if (jsonObj instanceof JSONObject) {
            Object pathResult = JSONPath.eval(jsonObj, exportAnnotation.contentJsonPath());
            if (!(pathResult instanceof JSONArray)) {
                throw new RuntimeException("contentJsonPath is not JSONArray");
            }
            content = (JSONArray) pathResult;
        }

        return content;
    }

}
